package com.inspinia.base.web.controller;

import com.inspinia.base.common.exception.BusinessException;
import com.inspinia.base.common.model.AjaxResult;
import com.inspinia.base.util.ContainerUtil;
import com.inspinia.base.util.JsonUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.util.List;

/**
 * 统一的异常处理类
 * Created by veblen on 16-1-13.
 */
@ControllerAdvice
public class AdviceController {

    /**
     * 日志对象
     */
    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 判断请求是否是ajax请求
     *
     * @param request
     * @return true表示是ajax请求
     */
    private boolean isAjax(HttpServletRequest request) {
        return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
    }

    /**
     * 404资源没有异常
     * todo 验证发现没有效果,等待继续发现其他方法
     *
     * @param request
     * @param response
     * @throws IOException
     */
    @ResponseStatus(value = HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    public void noHandlerFoundException(HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (isAjax(request)) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            ContainerUtil.outputString(response,"没有找到资源");
        } else {
            response.sendRedirect("/error-404");
        }
    }

    /**
     * 授权登录异常
     */
    @ExceptionHandler({UnauthorizedException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public void authenticationException(UnauthorizedException e, HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (isAjax(request)) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            ContainerUtil.outputString(response,"请重新登陆");
        } else {
            response.sendRedirect("/login");
        }
    }


    /**
     * 统一验证异常处理
     *
     * @param ex       绑定异常，如果validator有异常，META-INF mvc会抛出此异常
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public void processValidationError(BindException ex, HttpServletRequest request, HttpServletResponse response) throws IOException {
        logger.error("绑定异常", ex);
        if (isAjax(request)) {
            request.setCharacterEncoding("utf8");
            BindingResult result = ex.getBindingResult();
            List<FieldError> fieldErrors = result.getFieldErrors();
            JsonUtils.toJson(fieldErrors);
            String jsonString = JsonUtils.toJson(fieldErrors);
            ContainerUtil.outputString(response,jsonString);
        } else {
            logger.error("绑定异常", ex);
            response.sendRedirect("error-404");
        }
    }

    /**
     * 服务端异常统一处理
     *
     * @param e
     * @param request
     * @param response
     * @throws IOException
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public void handleValidationException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {
        logger.error("服务端异常", e);
        if (isAjax(request)) {
            String message = e.getMessage();
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            ContainerUtil.outputString(response,message);
        } else {
            response.sendRedirect("error-500");
        }
    }

    /**
     * 服务端异常统一处理
     *
     * @param e
     * @param request
     * @param response
     * @throws IOException
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public void handleValidationException(BusinessException e, HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (isAjax(request)) {
            request.setCharacterEncoding("utf8");
            String message =JsonUtils.toJson(new AjaxResult(false, org.apache.commons.lang3.StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : "服务端异常"));
            response.setStatus(HttpStatus.OK.value());
            ContainerUtil.outputString(response,message);
        } else {
            response.sendRedirect("500");
        }
    }

    /**
     * 授权登录异常
     */
    @ExceptionHandler({AuthenticationException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public void authenticationException(HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (isAjax(request)) {
            ContainerUtil.outputString(response,"请重新登陆");
        } else {
            response.sendRedirect("home");
        }
    }

    /**
     * 初始化数据绑定
     * 1. 将所有传递进来的String进行HTML编码，防止XSS攻击
     */
    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        // String类型转换，将所有传递进来的String进行HTML编码，防止XSS攻击
        binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
            }

            @Override
            public String getAsText() {
                Object value = getValue();
                return value != null ? value.toString() : "";
            }
        });
    }

}
